package com.tanx.cqrs.infrastructure.spring.command.handler;

import com.tanx.cqrs.aggregate.Aggregate;
import com.tanx.cqrs.command.Command;
import com.tanx.cqrs.command.handler.CommandHandlerInstance;
import com.tanx.cqrs.command.handler.CommandMeta;
import com.tanx.cqrs.eventsourcing.EventSourcingMata;
import lombok.Data;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * spring bean的命令处理器实例
 */
@Data
public class SpringBeanCommandHandlerInstance implements CommandHandlerInstance {
    private final Parameter[] parameters;
    private Object bean;
    private Method method;
    private Class<? extends Command> commandClass;
    private boolean aggregate = false;
    private List<CommandMeta> commandMetaList = new ArrayList<>();

    @SuppressWarnings("unchecked")
    public SpringBeanCommandHandlerInstance(Object bean, Method method) {
        this.bean = bean;
        this.method = method;
        parameters = method.getParameters();
        for (Class item : method.getParameterTypes()) {
            if (Command.class.isAssignableFrom(item)) {
                commandClass = item;
                break;
            }
        }
        if (bean.getClass().getAnnotation(Aggregate.class) != null) {
            this.aggregate = true;
        }
        commandMetaList.addAll(CommandMetaFactory.create(this));
    }

    @Override
    public List<EventSourcingMata> getEventSourcingMetas(Command command) {
        List<EventSourcingMata> matas = new ArrayList<>();
        for (CommandMeta meta : commandMetaList) {
            matas.add(new EventSourcingMata<>((Class<?>) meta.getTargetClass(), meta.getFieldValue(command)));
        }
        return matas;
    }

    @Override
    public Object invoke(Command command, Object... parameters) {
        try {
            method.setAccessible(true);
            if (aggregate) {
                Object target = null;
                for (Object parameter : parameters) {
                    target = parameter;
                }
                return method.invoke(target, command);
            } else {
                return method.invoke(bean, command, parameters);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
